package com.platform.module.common.redis;

import com.platform.module.common.base.CodeEnum;
import com.platform.module.common.base.CommonResp;
import com.platform.module.common.exception.CommonException;
import com.platform.module.common.redis.lock.DistributedLock;
import com.platform.module.common.redis.lock.RedisLock;
import com.platform.module.common.utils.IPUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;

@Order(4)
@Aspect
@Configuration
@ConditionalOnClass({DistributedLock.class})
@AutoConfigureAfter({DistributedLockAutoConfiguration.class})
public class DistributedLockAspectConfiguration {
    private final Logger logger = LoggerFactory.getLogger(DistributedLockAspectConfiguration.class);

    private static final String UNDERLINE = "_";

    public static ThreadLocal<RedisLock> lockTL = new ThreadLocal<>();

    private final DistributedLock distributedLock;

    public DistributedLockAspectConfiguration(DistributedLock distributedLock) {
        this.distributedLock = distributedLock;
    }

    @Pointcut("@annotation(com.platform.module.common.redis.lock.RedisLock)")
    private void lockPoint() {
    }

    @Around("lockPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        lockTL.set(redisLock);
        String key = redisLock.key();
        CodeEnum codeEnum = redisLock.codeEnum();

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String imei = request.getHeader("imei");
        if (StringUtils.isEmpty(imei))
            imei = IPUtils.getIpFromRequest(request);
        if (StringUtils.isEmpty(key))
            key = request.getRequestURI().replace("/", "_");
        if (redisLock.scope().equals(RedisLock.LockScope.OWN))
            key = key + "_" + imei;

        // 是否取特定参数识别
        Object[] params = pjp.getArgs();
        StringBuilder ukString = new StringBuilder();
        if (redisLock.isLockFiled()) {
            int[] lockFiled = redisLock.lockFiled();
            for (int i = 0; i < lockFiled.length; i++) {
                ukString.append(params[i].toString());
            }
        }

        key = "LOCK" + key + ukString;
        int retryTimes = redisLock.action().equals(RedisLock.LockFailAction.RETRY) ? redisLock.retryTimes() : 0;
        boolean lock = this.distributedLock.lock(key, redisLock.keepMills(), retryTimes, redisLock.sleepMills());
        if (!lock) {
            this.logger.warn("手速太快了，请稍微休息会儿！key={}", key);
            if (StringUtils.isNotBlank(key) && !key.contains("dataReport.api") && !key.contains("recordUpload.sw"))
                return CommonResp.fail(codeEnum);
        }
        if (logger.isDebugEnabled()) {
            this.logger.debug("get lock success : " + key);
        }
        try {
            return pjp.proceed();
        } catch (Exception e) {
            if (e instanceof CommonException) {
                this.logger.error("{}", ((CommonException) e).getMsg());
                return CommonResp.fail(((CommonException) e).getCode(), ((CommonException) e).getMsg());
            }
            this.logger.error("execute locked method occurred an exception", e);
            return CommonResp.fail(CodeEnum.FAILED);
        } finally {
            if (RedisLock.UnLockMode.AUTO.equals(redisLock.unlockMode())) {
                boolean releaseResult = this.distributedLock.releaseLock(key);
                this.logger.debug("release lock : " + key + (releaseResult ? " success" : " failed"));
            } else {
                this.logger.debug("don't release lock : " + key);
            }
        }
    }
}
